/*  dreic -- a compiler for Drei
 *
 *  $Id: Parser.scala 3085 2006-11-08 17:58:09Z dubochet $
 */

package dreic

/**
 * This class implements the parser of the drei compiler.
 */
class Parser(in: java.io.InputStream) extends Scanner(in) {
  import Tokens._
  
  val firstStatement = List(WHILE,IF,SET,DO,PRINTINT,PRINTCHAR,LBRACE);
  val firstSumOp = List(PLUS,MOINS,OR);
  val firstCompOp = List(EGAL,NEGAL,PETIT,GRAND,PTOUEG,GROUEG);
  val firstNegateOp = List(MOINS,NEG);
  val firstProdOp = List(PROD,QUOT,PERCENT,AND);
  val firstFactor = List(IDENT,NUMBER,STRING,TRUE,FALSE,THIS,READINT,READCHAR,LPAREN,LBRACE,NEW);
  
  /**
   * Reports an error that a specific token was expected but
   * a different token found.
   */
  private def error(expected: Token): Unit =
    error("token of class " + expected)


  /**
   * Reports an error that a specific token was expected but
   * a different token found.
   */
  private def error(expected: String): Unit = {
    val message = "syntax error\n" +
      "expected: " + expected + "\n" +
      "found   : " + representation
    Report.fail(startpos, message)
  }
  
  private def accept(expected: Token): Unit =
    if (token == expected)
      nextToken;
    else
      error(expected);
  
  def parse(): Program = {
    program;
  }
  
  private def program(): Program = {
    var classes : List[ClassDef] = Nil;
    while (token == CLASS) classes = classes:::List(classdecl);
    val body = statement;
    accept(EOF);
    return new Program(classes, body).setPos(startpos);
  }
  
  private def classdecl(): ClassDef = {
    var name_ext : Option[Name] = None;
    var members : List[Member] = Nil;
    accept(CLASS);
    //Console.println("chars =" + chars);
    val name_class = Name(chars);
    accept(IDENT);
    if (token == EXTENDS){
      accept(EXTENDS);
      accept(IDENT);
      name_ext = Some(Name(chars));
    }
    accept(LBRACE);
    while(token != RBRACE) members = members:::List(member);
    accept(RBRACE);
    return ClassDef(name_class, name_ext, members).setPos(startpos);
  }
  
  private def member(): Member = token match{
    case DVAL => fielddecl;
    case DEF => methoddef;
    case others => error("val or def"); null;
  }
  
  private def fielddecl(): FieldDecl = {
    accept(DVAL);
    val f = formal;
    accept(POINTV);
    return FieldDecl(f._1, f._2).setPos(startpos);
  }
  
  private def methoddef : MethodDef = {
    val header = methodheader;
    var t : Option[TypeTree] = None;
    var e : Expr = null;
    token match{
      case DPOINT => {
	accept(DPOINT);
	t = Some(Type);
	accept(AFFECT);
	e = expression;
	accept(POINTV);
      }
      case LBRACE =>{
	accept(LBRACE);
	e = new Block(statements, None);
	accept(RBRACE);
      }
      case others => error(": or {"); null;
    }
    return MethodDef(header._1, header._2, t, e).setPos(startpos);
  }
  
  private def methodheader : Pair[Name, List[Pair[Name, TypeTree]]] = {
    var args : List[Pair[Name, TypeTree]] = Nil;
    accept(DEF);
    accept(IDENT);
    val name = Name(chars);
    accept(LPAREN);
    token match{
      case IDENT => {
	args = args:::List(formal);
	while (token == VIRGU){
	  accept(VIRGU);
	  args = args:::List(formal);
	}
	accept(RPAREN);
      }
      case RPAREN => {
	accept(RPAREN);
      }
      case others => error("IDENT or )");
    }
    return Pair(name, args);
  }
  
  private def formal : Pair[Name, TypeTree] = {
    accept(IDENT);
    val name = new Name(chars);
    accept(DPOINT);
    return Pair(name,Type);
  }
  
  private def Type : TypeTree = token match{
    case INT => accept(INT); IntType;
    case IDENT => accept(IDENT); new ClassType(new Name(chars)).setPos(startpos);
    case others => error("int or IDENT"); null;
  }
  
  private def vardecl : Var = {
    accept(VAR);
    val f = formal;
    accept(AFFECT);
    val e = expression;
    accept(POINTV);
    return Var(f._1, f._2, e);
  }
  
  private def statements : List[Stat] = {
    var stats : List[Stat] = Nil;
    while (token==VAR || firstStatement.contains(token)){
      if (token==VAR){
	stats = stats ::: List(vardecl);
      }else{
	stats = stats ::: List(statement);
      }
    }
    return stats;
  }
  
  private def statement : Stat = token match {
    case WHILE => {
      accept(WHILE);
      accept(LPAREN);
      val e = expression;
      accept(RPAREN);
      val s = statement;
      s match{
	case Compound(a) => return While(e, a);
	case others => return While(e, List(s));
      }
    }
    case IF => {
      accept(IF);
      accept(LPAREN);
      val e = expression;
      //var iffalse: Stat = Do(NullLit());
      var iffalse: Stat = Compound(Nil);
      accept(RPAREN);
      val iftrue = statement;
      if (token==ELSE){
	accept(ELSE);
	iffalse = statement;
      }
      return If(e, iftrue, iffalse);
    }
    case SET => {
      accept(SET);
      accept(IDENT);
      val name = Name(chars);
      accept(AFFECT);
      val e = expression;
      accept(POINTV);
      return Set(name, e);
    }
    case DO => {
      accept(DO);
      val e = expression;
      accept(POINTV);
      return Do(e);
    }
    case PRINTINT => {
      accept(PRINTINT);
      accept(LPAREN);
      val e = expression;
      accept(RPAREN);
      accept(POINTV);
      return PrintInt(e);
    }
    case PRINTCHAR => {
      accept(PRINTCHAR);
      accept(LPAREN);
      val e = expression;
      accept(RPAREN);
      accept(POINTV);
      return PrintChar(e);
    }
    case LBRACE => {
      accept(LBRACE);
      val s = statements;
      accept(RBRACE);
      return Compound(s);
    }
    case others =>
      error("while, if, set, do, printInt, printChar, {");
      null
  }
  
  
  //Expression = SumExpression [CompOp SumExpression]
  private def expression : Expr = {
    val l = sumexpression;
    if (firstCompOp.contains(token)){
      val o = compop;
      val r = sumexpression;
      return Binop(o, l, r);
    }
    return l;
  }
  
  private def compop : B = token match{
    case EGAL => nextToken; Eq;
    case NEGAL => nextToken; Ne;
    case PETIT => nextToken; Lt;
    case GRAND => nextToken; Gt;
    case PTOUEG => nextToken; Le;
    case GROUEG => nextToken; Ge;
    case others => error("== or != or < or > or <= or >="); null;
  }
  //SumExpression  = Term [Sumop SumExpression]
  private def sumexpression : Expr = {
    var l = term;
    while (firstSumOp.contains(token)){
      val o = sumop;
      val r = term;
      l = o match {
	case Or() => Unop(Not, Binop(And, Unop(Not, l), Unop(Not, r)))
	case others => Binop(o, l, r);
      }
    }
    l;
  }
  
  private def sumop : B = token match {
    case PLUS => nextToken; Add;
    case MOINS => nextToken; Sub;
    case OR => nextToken; Or;
    case others => error("+ or -"); null;
  }

  /*
  Factor1   = ident
               | number
               | string
               | "true"
               | "false"
               | "this"
               | "readInt"
               | "readChar"
               | "(" Expression ")"
               | "{" Statements "return" Expression "}"
               | "new" ident Arguments
  */
  private def factor1 : Expr = token match{
    case IDENT =>
      accept(IDENT);
      return Ident(Name(chars));
    
    case NUMBER =>
      accept(NUMBER);
      return IntLit(Integer.valueOf(chars).intValue());
    
    case STRING =>
      accept(STRING);
      val str = chars.toList.foldRight(New(Name("Nil"),Nil))((x, xs) =>
	      New(Name("Cons"), IntLit(x.asInstanceOf[Int])::List(xs)));
      if (chars == "") return New(Name("Nil"), Nil);
      return str;
    
    case TRUE =>
      accept(TRUE);
      return IntLit(1);
    
    case FALSE =>
      accept(FALSE);
      return IntLit(0);
    
    case THIS =>
      accept(THIS);
      return Ident(Name("this"));

    case READINT =>
      accept(READINT);
      return ReadInt;

    case READCHAR =>
      accept(READCHAR);
      return ReadChar;

    case LPAREN =>{
      accept(LPAREN);
      val e = expression;
      accept(RPAREN);
      return e;
    }
    case LBRACE =>{
      accept(LBRACE);
      val s = statements;
      accept(RETURN);
      val e = expression;
      accept(RBRACE);
      return Block(s, Some(e));
    }
    case NEW =>{
      accept(NEW);
      accept(IDENT);
      val name = Name(chars);
      val args = arguments;
      return New(name, args);
    }
    case others =>{
      error("factor");
      null;
    }
  }
  
  
  /*
    Factor = Factor1 | Factor1 { "." ident [ Arguments ] }
  */
    private def factor : Expr = {
      var f = factor1;
      while (token==POINT){
          accept(POINT);
          accept(IDENT);
          val name = Name(chars);
          f = if (token == LPAREN) Call(f, name, arguments) else {
              Select(f, name);
          }
      }
      f
    }

  // arguments = "(" [ Expressions ] ")"
private def arguments : List[Expr] = {
  var args: List[Expr] = Nil;
  accept(LPAREN);
  if (token!=RPAREN) args = expressions;
  accept(RPAREN);
  return args;
}

// Expression { "," Expression }
private def expressions : List[Expr] = {
  var exprs : List[Expr] = List(expression);
  while(token==VIRGU){
    nextToken;
    exprs = exprs:::List(expression);
  }
  return exprs;
}

private def term : Expr = {
  var o1 : U = null;
  var result : Expr = null;
  if (firstNegateOp.contains(token)) o1 = negateop;
  val f1 = factor;
  o1 match{
    case null =>
      result = f1;
    case others =>
      result = Unop(o1, f1);
  }
  while (firstProdOp.contains(token)){
    var nop : U = null;
    val pop = prodop;
    if (firstNegateOp.contains(token)) nop = negateop;
    val f = factor;
    nop match{
      case null =>
	result = Binop(pop, result, f);
      case others =>
	result = Binop(pop, result, Unop(nop, f));
    }
  }
  return result;
}

private def negateop : U = token match {
  case MOINS => nextToken; Neg;
  case NEG => nextToken; Not;
  case others => error("- or !"); null;
}

private def prodop : B = token match{
  case PROD => nextToken; Mul;
  case QUOT => nextToken; Div;
  case PERCENT => nextToken; Mod;
  case AND => nextToken; And;
  case others => error("* or / or % or &&"); null;
}

}
